package evs.identification;

import java.util.Hashtable;
import java.util.concurrent.Semaphore;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * containing the necessary register & lookup functionality for the naming service
 * implemented as remote object
 * 
 * 
 */
public class NameServerLookup implements NameServerProxyInterface {
	
	private Log log = LogFactory.getLog(NameServerLookup.class);
	private static final int TRANSACTION_MAX_DURATION_MSEC = 60000;
	
	
	
	/**
	 * naming service entries are identified by two keys
	 * 
	 * key: key1
	 * value: Hashtable (key: key2, value: object) 
	 */
	private Hashtable hashtable;
	private Hashtable semaphores;
	private Hashtable timestamps;
	private Hashtable lastCheckout;
	
	
	
	public NameServerLookup() {
		hashtable = new Hashtable<String, Hashtable>();
		semaphores = new Hashtable<String, Hashtable>();
		timestamps = new Hashtable<String, Hashtable>();
		lastCheckout = new Hashtable<String, Hashtable>();
	}

	/**
	 * adds an object, which is identified by two keys, to the naming service
	 * 
	 * synchronized to avoid concurrency problems when the same client performs multiple
	 * put operations at the same time 
	 * 
	 * @param key1 key1 of entry
	 * @param key2 key2 of entry
	 * @param object object which should be accessible through naming service
	 * @return object to be added to naming service if successful, null else
	 */
	public Object putEntry(String key1, String key2, Object object) {
		
		boolean semaphore_released = false;
		Semaphore accordingObjectSemaphore = null;
		
		try {
			
			Object returnObject = null;
			Long accordingTimeStamp = null;
			String accordingLastCheckout = null;
			
			Hashtable innerTable = null;
			
			/**
			 * get client id of last checkout
			 */
			if(lastCheckout.containsKey(key1)) {
				innerTable = (Hashtable)lastCheckout.get(key1);
				if(innerTable != null) {
					accordingLastCheckout = (String) innerTable.get(key2);
				}
			}

			/**
			 * get / create according semaphore
			 */
			if(!semaphores.containsKey(key1)) {
				innerTable = new Hashtable<String, Semaphore>();
				accordingObjectSemaphore = new Semaphore(0);
				innerTable.put(key2, accordingObjectSemaphore);
				semaphores.put(key1, innerTable);
			} else {
				innerTable = (Hashtable)semaphores.get(key1);
				accordingObjectSemaphore = (Semaphore) innerTable.get(key2);
				if(accordingObjectSemaphore == null) {
					accordingObjectSemaphore = new Semaphore(0);
					innerTable.put(key2, accordingObjectSemaphore);
				}
			}	

			/**
			 * check if timestamp of last access was too long ago -> so we can't put the object in
			 * -> since another one maybe has already checked it out since we are too late
			 */
			if(timestamps.containsKey(key1)) {
				innerTable = (Hashtable)timestamps.get(key1);
				if(innerTable != null) {
					accordingTimeStamp = (Long) innerTable.get(key2);
					if(accordingTimeStamp != null) {
						long time_difference = System.currentTimeMillis() - accordingTimeStamp.longValue();
						
						if(time_difference > TRANSACTION_MAX_DURATION_MSEC) {
							return null;
						}
					}
				}
			}

			/**
			 * check if last object also was checked out by the client who wants to override it
			 */
			if(accordingLastCheckout != null) {
				if(accordingLastCheckout.compareTo(key1) != 0) {
					return null;
				}
			}

			/**
			 * put object
			 */
			if (!hashtable.containsKey(key1)) {
				innerTable = new Hashtable<String, Object>();
				innerTable.put(key2, object);
				returnObject = object;
				hashtable.put(key1, innerTable);
			} else {
				innerTable = (Hashtable)hashtable.get(key1);
				innerTable.put(key2, object);
				returnObject = object;
			}

			accordingObjectSemaphore.release(1);
			semaphore_released = true;
			
			/**
			 * touch timestamp
			 */
			if(!timestamps.containsKey(key1)) {
				innerTable = new Hashtable<String, Long>();
				innerTable.put(key2, new Long(System.currentTimeMillis()));
				timestamps.put(key1, innerTable);
			} else {
				innerTable = (Hashtable)timestamps.get(key1);
				innerTable.put(key2, new Long(System.currentTimeMillis()));
			}

			return returnObject;
			
		}catch (Exception exc) {
			
			try {
				if(semaphore_released)
					accordingObjectSemaphore.acquire(1);
			} catch (InterruptedException ie) {
				ie.printStackTrace();
			}
			
			exc.printStackTrace();
			return null;
		}
	}

	/**
	 * @return object identified by given keys if existing, else null
	 */
	public Object getEntry(String key1, String key2) {
		
		boolean semaphore_acquired = false;
		Semaphore accordingObjectSemaphore = null;
		
		try {

			Object returnObject = null;
			Long accordingTimeStamp = null;
			
			Hashtable innerTable = null;
			
			/**
			 * get / create according semaphore
			 */
			if(!semaphores.containsKey(key1)) {
				innerTable = new Hashtable<String, Semaphore>();
				accordingObjectSemaphore = new Semaphore(1);
				innerTable.put(key2, accordingObjectSemaphore);
				semaphores.put(key1, innerTable);
			} else {
				innerTable = (Hashtable)semaphores.get(key1);
				accordingObjectSemaphore = (Semaphore) innerTable.get(key2);
				if(accordingObjectSemaphore == null) {
					accordingObjectSemaphore = new Semaphore(1);
					innerTable.put(key2, accordingObjectSemaphore);
				}
			}	
				
			/**
			 * check if timestamp of last access was too long ago -> so we can assume that some checked the object out
			 * and because of some reason he won't return it -> so we may use it
			 */
			if(timestamps.containsKey(key1)) {
				innerTable = (Hashtable)timestamps.get(key1);
				if(innerTable != null) {
					accordingTimeStamp = (Long) innerTable.get(key2);
					if(accordingTimeStamp != null) {
						long time_difference = System.currentTimeMillis() - accordingTimeStamp.longValue();
						
						/**
						 * time gap too big -> we will occupy the object
						 */
						if(time_difference > TRANSACTION_MAX_DURATION_MSEC) {
							if(accordingObjectSemaphore.availablePermits() == 0)
								accordingObjectSemaphore.release(1);
						}
					}
				}
			}			

			/**
			 * get object
			 */
			accordingObjectSemaphore.acquire(1);
			semaphore_acquired = true;
				
			innerTable = (Hashtable)hashtable.get(key1);
			if(innerTable != null) 
				returnObject = innerTable.get(key2);
			if(returnObject == null) {
				accordingObjectSemaphore.release(1);
				semaphore_acquired = false;
				return null;
			}

			/**
			 * touch timestamp
			 */
			if(!timestamps.containsKey(key1)) {
				innerTable = new Hashtable<String, Long>();
				innerTable.put(key2, new Long(System.currentTimeMillis()));
				timestamps.put(key1, innerTable);
			} else {
				innerTable = (Hashtable)timestamps.get(key1);
				innerTable.put(key2, new Long(System.currentTimeMillis()));
			}

			/**
			 * save last checkout info
			 */
			if(!lastCheckout.containsKey(key1)) {
				innerTable = new Hashtable<String, String>();
				innerTable.put(key2, key1);
				lastCheckout.put(key1, innerTable);
			} else {
				innerTable = (Hashtable)lastCheckout.get(key1);
				innerTable.put(key2, key1);
			}
			
			return returnObject;
		
		}catch (Exception exc) {
			
			if(semaphore_acquired)
				accordingObjectSemaphore.release(1);
			
			exc.printStackTrace();
			return null;
		}
		

	}
	
	/**
	 * @param key1
	 * @param key2
	 * @return
	 */
	public Object cleanup(String key1, String key2) {
		
		try {
			
			if(hashtable.containsKey(key1))
				((Hashtable)hashtable.get(key1)).remove(key2);
			if(semaphores.containsKey(key1))
				((Hashtable)semaphores.get(key1)).remove(key2);
			if(timestamps.containsKey(key1))
				((Hashtable)timestamps.get(key1)).remove(key2);
			if(lastCheckout.containsKey(key1))
				((Hashtable)lastCheckout.get(key1)).remove(key2);
			
			return null;
			
		} catch (Exception exc) {
			exc.printStackTrace();
			return null;
		}
		
	}
	

}
